
//
// 101029 - AH - Created.
// 101031a- AH - Now all keywords must been matched, removed methods related to 
//               that this may not be the case and constructor now throws an 
//               IllegealArgumentException if not all keywords matches.
// 101031b- AH - Made constructor public, need for testing.
// 101108a- AH - Single keywords case passed unit tests. 
// 101109 - AH - Oh, had forgot tagOnly(), fixed.
// 101110 - AH - Now that all methods implemented after Laetitia's spec. and
//               used in tested retrieval algorithm removed my old commented
//               away code.
// 101110b- AH - Missing java doc. added.
// 101121a- AH - toString() no says "empty" if m_matched is empty.
// 101121b- AH - noMatch().
// 101205 - AH - Renamed from MineMatched.
// 101208 - AH - Moved from org.soekrat.engine.wheel
// 101225 - AH - Removed getRandom implemented here, now has generic:
//               org.soekrat.engine.Util.getRandom.
// 101230 - AH - getMine().
// 110518 - AH - (Chaos) Logic that first try match complete sentence in place.
// 110602 - AH - Took in use sp arg. in constructor (had only been used for
//               matching signature) and added missing java doc.
// 111001 - AH - Added TODO in yet not implemented method 
//               getNotRetrievedYet(List<AttributeObject>).
//

package org.soekrat.engine.retrieval.mine;

import static org.soekrat.engine.util.Util.getRandom;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.soekrat.AttributeObject;
import org.soekrat.engine.mine.Mine;
import org.soekrat.engine.mine.Resource;
import org.soekrat.engine.ontology.OntologyDatastructure;
import org.soekrat.engine.sparewheel.SpareWheel;
import org.soekrat.engine.visitor.Input;
import org.soekrat.engine.wheel.Wheel;

/**
 * <p>
 *   Represents matches keywords did in a
 *   {@link Mine}.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC)
 */
public final class MineScan 
{
  private final List<MineScanDetail> m_matched=new ArrayList<MineScanDetail>();
  
  private final Set<String> m_keywords=new HashSet<String>();
  
  private final Mine m_mine;
  
  /////// Sparewheel
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param mine  {@link OntologyDatastructure} to match in.
   * @param input {@link Input} to match.
   * @param sp    If {@code true} created for 
   *              {@link SpareWheel} retrieval, if {@code false} creates for
   *              {@link Wheel} retrieval.
   */
  public MineScan(Mine mine, Input input, boolean sp)
  {
    if (mine==null)
    {
      throw new NullPointerException("mine");
    }
    
    m_mine=mine;
    
    if (sp) init4sw(input);
    else    init4w(input);
  }
  
  ///////
  
  /**
   * <p>
   *   Creates for 
   *   {@link Wheel} retrieval.
   * </p>
   * @param mine  {@link Mine} to find matching resources in.
   * @param input Keywords to match with.
   */
  public MineScan(Mine mine, Input input)
  {
    if (mine==null)
    {
      throw new NullPointerException("mine");
    }
   
    m_mine=mine;
    
    init4w(input);
  }
  
  /**
   * <p>
   *   Called from constructors to initial for
   *   {@link SpareWheel} retrieval matching.
   * </p>
   * @param input {@link Input}.
   */
  private void init4sw(Input input)
  {
    if (input==null)
    {
      throw new NullPointerException("keywords");
    }
    
    AttributeObject[] matched=m_mine.match(input);
    for (AttributeObject m : matched) 
    {
      MineScanDetail match=new MineScanDetail(m, input);
    
      String[] keys=match.geKeywordsThatMatched();
      for (String key : keys) m_keywords.add(key);
    
      m_matched.add(match);
    }
  }
  
  /**
   * <p>
   *   Called from constructors to initial for
   *   {@link Wheel} retrieval matching.
   * </p>
   * @param input {@link Input}.
   */
  private void init4w(Input input)
  {
    if (input==null)
    {
      throw new NullPointerException("keywords");
    }
    
    AttributeObject[] matched=m_mine.match(input);
    setScanResult(matched, input);
  }
  
  private void setScanResult(AttributeObject[] matched, Input input)
  {
    for (AttributeObject m : matched) 
    {
      MineScanDetail match=new MineScanDetail(m, input);
      
      String[] keys=match.geKeywordsThatMatched();
      for (String key : keys) m_keywords.add(key);
      
      m_matched.add(match);
    }
  }
  
  /**
   * <p>
   *   Gets
   *   {@link Mine} scanned.
   * </p>
   * @return {@code Mine}.
   */
  public Mine getMine(){ return m_mine; }
  
  /**
   * <p>
   *   Tells if nothing was matched.
   * </p>
   * @return {@code true} if nothing matched, {@code false} else.
   */
  public boolean noMatch(){ return m_matched.isEmpty(); }
  
  @Override
  public String toString() 
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    if (m_matched.isEmpty())
    {
      pw.println("empty");
    }
    else
    {
      for (MineScanDetail curr : m_matched)
      {
        pw.println(curr);  
      }
    }
    
    pw.flush();
    return caw.toString();
  }
  
  /**
   * <p>
   *   Gets all resources matched by name only, name and tag(s) or tag(s) only. 
   * </p>
   * @return Resources.
   */
  public List<AttributeObject> getListOfMatched()
  { 
    return retrieveResources(m_matched); 
  }
  
  /**
   * <p>
   *   Tells if multiple keywords was matched resources.
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean multiKeywords(){ return m_keywords.size()>1; }
  
  /**
   * <p>
   *   Tells if resources has been matched by name only.
   * </p>
   * @return {@code true} if is the case, {@code false} if not.
   */
  public boolean nameOnly()
  {
    for (MineScanDetail curr : m_matched)
    {
      if (!curr.isNameMatched()) return false;
      if (curr.isTagMatched()) return false;
    }
    return true;
  }
  
  /**
   * <p>
   *   Gets resources matched by name.
   * </p>
   * @return Found.
   */
  public List<Resource> getName()
  {
    List<Resource> retVal=new ArrayList<Resource>();
    
    for (MineScanDetail curr : m_matched)
    {
      if (curr.isNameMatched()) retVal.add((Resource)curr.getResource()); 
    }
    
    return retVal;
  }
  
  /**
   * <p>
   *   Gets resources that is matched by name only.
   * </p>
   * @return Resources.
   */
  public List<AttributeObject> getByName()
  {
    List<MineScanDetail> l=new ArrayList<MineScanDetail>();
    for (MineScanDetail curr : m_matched)
    {
      if (curr.isNameMatched() && !curr.isTagMatched()) l.add(curr);
    } 
    
    return retrieveResources(l);
  }
  
  /**
   * <p>
   *   Tells if resources was matched by name and tags.
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean nameTag()
  {
    boolean name=false;
    boolean tag=false;
    int n=m_matched.size();
    for (int i=0; i<n; i++)
    {
      MineScanDetail curr=m_matched.get(i);
      if (curr.isNameMatched()==true) name=true;
      if (curr.isTagMatched()==true) tag=true;
    }
    
	  return name && tag;
  }
  
  /**
   * <p>
   *   Tells if there is at least one resource matched by name and tag(s).
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean tagNameTogether()
  {
    for (MineScanDetail curr : m_matched)
      if (curr.isNameMatched() && curr.isTagMatched()) return true;
    return false;
  }
  
  /**
   * <p>
   *   Gets resources that are matched by name and tag(s).
   * </p>
   * @return Resources.
   */
  public List<AttributeObject> getTagNameTogether()
  {
	  List<MineScanDetail> l=new ArrayList<MineScanDetail>();
    
    for (MineScanDetail curr : m_matched)
      if (curr.isNameMatched() && curr.isTagMatched()) l.add(curr); 
    return retrieveResources(l);
  }
  
  /**
   * <p>
   *   Tells if some resources was matched by name only and that some resources
   *   was matched by tags (but then not by name).  
   * </p>
   * @return {@code true} if is the case, {@code false} if not.
   */
  public boolean tagNameSeparated()
  {
	  boolean name=false;
    boolean tag=false;
    int n=m_matched.size();
    for (int i=0; i<n; i++)
    {
      MineScanDetail curr=m_matched.get(i);
      
      if (curr.isNameMatched()==true && curr.isTagMatched()==false)
      {
        name=true;
      }
      else if (curr.isTagMatched()==true && curr.isNameMatched()==false)
      {
        tag=true;
      }
    }
    
	  return name && tag;
  }
  
  /**
   * <p>
   *   Tells if resources was matched by tags only.
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean tagOnly()
  {
    int n=m_matched.size();
    for (int i=0; i<n; i++)
    {
      MineScanDetail curr=m_matched.get(i);
      if (curr.isNameMatched()) return false;
    }
    
    return true;
  }
 
  // Invoked from methods that returns list of resources. 
  private static List<AttributeObject> retrieveResources(List<MineScanDetail> l)
  {
    List<AttributeObject> retVal=new ArrayList<AttributeObject>();
    
    for (MineScanDetail curr : l) 
    {
      AttributeObject resource=curr.getResource();
      int n=curr.getNumberOfTagsMatched();
      resource.setAttribute("tag-match-count", n);
      retVal.add(resource);
    }
    return retVal;
  }
  
  /**
   * <p>
   *   Returns the list of resources matched by most tags.
   * </p>
   * @return List.
   */
  public static List<AttributeObject> getMostTagged(List<AttributeObject> l)
  {
    return getMostTagged(l, 0);
  }
  
  //
  // Sorts list tag count, used by getMostTagged method.
  //
  private static final Comparator<AttributeObject> 
    m_resTagMatchCountComp=new Comparator<AttributeObject>()
  {
    @Override
    public int compare(AttributeObject o1, AttributeObject o2)
    {
      Integer o1c=(Integer)o1.getAttribute("tag-match-count");
      Integer o2c=(Integer)o2.getAttribute("tag-match-count");
      
      if (o1c==null)
      {
        throw new IllegalStateException("missing tag-match-count on o1");
      }
      if (o2c==null)
      {
        throw new IllegalStateException("missing tag-match-count on o2");
      }
      
      return o1c<o2c ? -1 : (o1c==o2c ? 0 : 1);
    } 
  };
  
  /**
   * <p>
   *   Returns the list of resources matched most tags but with at least 
   *   {@code min} tags.
   * </p>
   * @return List.
   */
  public static List<AttributeObject> getMostTagged(List<AttributeObject> l, 
    int min)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    if (min<0)
    {
      throw new IllegalArgumentException("min<0 : "+min);
    }
    
    List<AttributeObject> retVal=new ArrayList<AttributeObject>();
    
    if (l.isEmpty()) return retVal;
    
    Collections.sort(l, m_resTagMatchCountComp);
    
    int max=-1;
    
    int n=l.size();
    int nm1=n-1;
    for (int i=nm1; i>=0; i--)
    {
      AttributeObject curr=l.get(i);
      Integer count=(Integer)curr.getAttribute("tag-match-count");
      if (i==nm1)
      {
        if (count==0 || count<min) return retVal;
        
        max=count;
        retVal.add(curr);
      }
      else
      {
        if (count==max) retVal.add(curr);
        else            return retVal;
      }
    }
    
    return retVal;
  }
  
  /**
   * <p>
   *   Retrieves a resource from given list that has not been retrieved yet.
   * </p>
   * @param l List to retrieve from.
   * @return Random picked element or {@code null} if {@code l} is empty or all
   *         has been retrieved.
   */
  public static AttributeObject getNotRetrievedYet(List<AttributeObject> l)
  {
    // TODO: DUMMY IMPL.
    
    Random rnd=new Random();
    boolean allRetrieved=rnd.nextBoolean();
    if (allRetrieved) return null;
    else              return getRandom(l);
  }
  
}
